import GameConfig from "./viewer/GameConfig";
import { Square } from "./Square";
import { SquareGroup } from "./SquareGroup";
import { MoveDirection, Point, Shape } from "./types";

/**
 * 判断当前对象是否是位置对象
 * @param obj
 * @returns
 */
function isPoint(obj: any): obj is Point {
  if (typeof obj.x == "undefined") {
    return false;
  }
  return true;
}

/**
 * 游戏规则类
 * 只提供静态方法
 */
export class TerisRules {
  /**
   * 当前形状是否可以移动到指定位置：至少有一个方块超出游戏面板范围
   * 已有方块的位置也是不可以移动的
   * @param shape 形状 方格的相对坐标组
   * @param targetPoint 中心点目标位置
   * @returns {boolean}
   */
  static canIMove(shape: Shape, targetPoint: Point, exits: Square[]): boolean {
    // 假设中心点已经移动到目标位置，计算出每个方块到坐标
    const targetShapePoint: Point[] = shape.map(it => {
      return {
        x: targetPoint.x + it.x,
        y: targetPoint.y + it.y
      };
    });
    // 边界判断：是否超出边界
    let result = targetShapePoint.some(p => {
      return p.x < 0 || p.x > GameConfig.panelSize.width - 1 || p.y < 0 || p.y > GameConfig.panelSize.height - 1;
    });
    if (result) {
      // 超出边界
      return false;
    }

    // 目标位置中，是否包含已落下方块的位置
    result = targetShapePoint.some(t => exits.some(e => e.point.x === t.x && e.point.y === t.y));
    if (result) {
      return false;
    }

    return true;
  }

  // 函数重载：多个传值方式
  static move(teris: SquareGroup, targetPoint: Point, exits: Square[]): boolean;
  static move(teris: SquareGroup, driection: MoveDirection, exits: Square[]): boolean;
  /**
   * 移动组合块到指定位置
   * @param teris
   * @param targetPoint
   * @returns {boolean} 是否移动成功
   */
  static move(teris: SquareGroup, targetPointOrDriection: Point | MoveDirection, exits: Square[]): boolean {
    // 按照目标位置移动
    if (isPoint(targetPointOrDriection)) {
      // 判断是否可以移动到目标位置
      if (this.canIMove(teris.shape, targetPointOrDriection, exits)) {
        teris.centerPoint = targetPointOrDriection;
        return true;
      }
      return false;
    } else {
      // 按照方向移动
      let targetPoint: Point;
      const driection = targetPointOrDriection;
      if (driection === MoveDirection.down) {
        targetPoint = {
          x: teris.centerPoint.x,
          y: teris.centerPoint.y + 1
        };
      } else if (driection === MoveDirection.left) {
        targetPoint = {
          x: teris.centerPoint.x - 1,
          y: teris.centerPoint.y
        };
      } else {
        targetPoint = {
          x: teris.centerPoint.x + 1,
          y: teris.centerPoint.y
        };
      }
      // 判断当前目标位置是否在范围内
      if (this.canIMove(teris.shape, targetPoint, exits)) {
        teris.centerPoint = targetPoint;
        return this.move(teris, targetPoint, exits);
      }
      return false;
    }
  }

  /**
   * 旋转的规则：不能超过边界
   * @param teris 形状
   */
  static rotate(teris: SquareGroup, exits: Square[]): boolean {
    // 得到旋转后新的形状，
    const afterShape = teris.afterRotateShape();
    if (this.canIMove(afterShape, teris.centerPoint, exits)) {
      teris.rotate();
      return true;
    } else {
      return false;
    }
  }

  /**
   * 直接移动到目标位置
   * @param teris
   * @param targetPoint
   */
  static moveDirectly(teris: SquareGroup, driection: MoveDirection, exits: Square[]) {
    while (this.move(teris, driection, exits)) {}
  }

  /**
   * 消除方块
   * @param exits
   * @returns {Number} 返回消除了几行
   */
  static deleteSquares(exits: Square[]): number {
    const ys = exits.map(e => e.point.y);
    const yMin = Math.min(...ys);
    const yMax = Math.max(...ys);
    let num = 0;
    for (let y = yMin; y <= yMax; y++) {
      // 找每一行的方块，如果方块个数===面板宽度，则该行可消除
      if (this.deleteLine(exits, y)) {
        num++;
      }
    }
    return num;
  }

  /**
   * 消除指定行数的方块
   * 消除：页面、数据
   * @param exits
   * @param y
   */
  private static deleteLine(exits: Square[], y: number): boolean {
    const squares = exits.filter(q => q.point.y === y);
    // 找每一行的方块，如果方块个数===面板宽度，则该行可消除
    if (squares.length === GameConfig.panelSize.width) {
      squares.forEach(sq => {
        // 1. 页面上移除
        sq.viewer?.remove();
        // 2. 数据上移除
        const index = exits.indexOf(sq);
        exits.splice(index, 1);
      });

      // 3. 把小于当前行 y 值的方块，统一向下移
      exits
        .filter(q => q.point.y < y)
        .forEach(it => {
          it.point = {
            x: it.point.x,
            y: it.point.y + 1
          };
        });

      return true;
    }

    return false;
  }
}
